#include <memory>

#include "common/elements/element.h"
#include "common/log/log.h"

#include "data_edge.h"
#include "data_element.h"
#include "data_field_type.h"
#include "data_item.h"
#include "data_item_field.h"
#include "data_item_history.h"
#include "data_item_type.h"
#include "data_item_user_state.h"
#include "data_phase.h"
#include "data_project.h"
#include "data_role.h"
#include "data_role_menu_item.h"
#include "data_rule.h"
#include "data_rule_item_type.h"
#include "data_rule_project.h"
#include "data_rule_state.h"
#include "data_state.h"
#include "data_team.h"
#include "data_user.h"
#include "data_user_team_role.h"
#include "data_workflow.h"

#include "data_element_maker.h"

namespace Database
{

namespace
{
template <typename T>
std::shared_ptr<DataElement> createDataElement(T element)
{
    if (nullptr == element)
        return nullptr;

    switch (element->elementType())
    {
    case Elements::ElementType::Item:
        return std::make_shared<DataItem>(element);
    case Elements::ElementType::ItemType:
        return std::make_shared<DataItemType>(element);
    case Elements::ElementType::FieldType:
        return std::make_shared<DataFieldType>(element);
    case Elements::ElementType::Workflow:
        return std::make_shared<DataWorkflow>(element);
    case Elements::ElementType::State:
        return std::make_shared<DataState>(element);
    case Elements::ElementType::Edge:
        return std::make_shared<DataEdge>(element);
    case Elements::ElementType::User:
        return std::make_shared<DataUser>(element);
    case Elements::ElementType::Role:
        return std::make_shared<DataRole>(element);
    case Elements::ElementType::Team:
        return std::make_shared<DataTeam>(element);
    case Elements::ElementType::UserTeamRole:
        return std::make_shared<DataUserTeamRole>(element);
    case Elements::ElementType::RoleMenuItem:
        return std::make_shared<DataRoleMenuItem>(element);
    case Elements::ElementType::Rule:
        return std::make_shared<DataRule>(element);
    case Elements::ElementType::RuleState:
        return std::make_shared<DataRuleState>(element);
    case Elements::ElementType::RuleItemType:
        return std::make_shared<DataRuleItemType>(element);
    case Elements::ElementType::RuleProject:
        return std::make_shared<DataRuleProject>(element);
    case Elements::ElementType::Project:
        return std::make_shared<DataProject>(element);
    case Elements::ElementType::Phase:
        return std::make_shared<DataPhase>(element);
    case Elements::ElementType::ItemHistory:
        return std::make_shared<DataItemHistory>(element);
    case Elements::ElementType::ItemUserState:
        return std::make_shared<DataItemUserState>(element);
    case Elements::ElementType::ItemField:
        return std::make_shared<DataItemField>(element);

    default:
        LOG_WARN
            << "Обнаружен нереализованный объект с типом: "
            << unsigned(element->elementType());
        break;
    }
    return nullptr;
}

} // namespace

DataElementPtr wrapAnElement(Elements::Element* element)
{
    return createDataElement(element);
}

DataElementPtr wrapAnSharedElement(Elements::ElementPtr element)
{
    return createDataElement(element);
}

DataElementPtr fromDatabase(
    sqlite::database& database,
    Elements::ElementType elementType,
    unsigned id
)
{
    if (0 == id)
        return nullptr;

    switch (elementType)
    {
    case Elements::ElementType::Item:
        return std::make_shared<DataItem>(database, id);
    case Elements::ElementType::ItemType:
        return std::make_shared<DataItemType>(database, id);
    case Elements::ElementType::FieldType:
        return std::make_shared<DataFieldType>(database, id);
    case Elements::ElementType::Workflow:
        return std::make_shared<DataWorkflow>(database, id);
    case Elements::ElementType::State:
        return std::make_shared<DataState>(database, id);
    case Elements::ElementType::Edge:
        return std::make_shared<DataEdge>(database, id);
    case Elements::ElementType::User:
        return std::make_shared<DataUser>(database, id);
    case Elements::ElementType::Role:
        return std::make_shared<DataRole>(database, id);
    case Elements::ElementType::Team:
        return std::make_shared<DataTeam>(database, id);
    case Elements::ElementType::UserTeamRole:
        return std::make_shared<DataUserTeamRole>(database, id);
    case Elements::ElementType::RoleMenuItem:
        return std::make_shared<DataRoleMenuItem>(database, id);
    case Elements::ElementType::Rule:
        return std::make_shared<DataRule>(database, id);
    case Elements::ElementType::RuleState:
        return std::make_shared<DataRuleState>(database, id);
    case Elements::ElementType::RuleItemType:
        return std::make_shared<DataRuleItemType>(database, id);
    case Elements::ElementType::RuleProject:
        return std::make_shared<DataRuleProject>(database, id);
    case Elements::ElementType::Project:
        return std::make_shared<DataProject>(database, id);
    case Elements::ElementType::Phase:
        return std::make_shared<DataPhase>(database, id);
    case Elements::ElementType::ItemHistory:
        return std::make_shared<DataItemHistory>(database, id);
    case Elements::ElementType::ItemUserState:
        return std::make_shared<DataItemUserState>(database, id);
    case Elements::ElementType::ItemField:
        return std::make_shared<DataItemField>(database, id);

    default:
        LOG_WARN
            << "Обнаружен нереализованный объект с типом: "
            << unsigned(elementType);
        break;
    }
    return nullptr;
}

DataElements fromDatabase(
    sqlite::database& database,
    Elements::ElementType elementType,
    const std::vector<unsigned>& ids
)
{
    DataElementPtr creator;
    switch (elementType)
    {
    case Elements::ElementType::Item:
        creator.reset(new DataItem(nullptr));
        break;
    case Elements::ElementType::ItemType:
        creator.reset(new DataItemType(nullptr));
        break;
    case Elements::ElementType::FieldType:
        creator.reset(new DataFieldType(nullptr));
        break;
    case Elements::ElementType::Workflow:
        creator.reset(new DataWorkflow(nullptr));
        break;
    case Elements::ElementType::State:
        creator.reset(new DataState(nullptr));
        break;
    case Elements::ElementType::Edge:
        creator.reset(new DataEdge(nullptr));
        break;
    case Elements::ElementType::User:
        creator.reset(new DataUser(nullptr));
        break;
    case Elements::ElementType::Role:
        creator.reset(new DataRole(nullptr));
        break;
    case Elements::ElementType::Team:
        creator.reset(new DataTeam(nullptr));
        break;
    case Elements::ElementType::UserTeamRole:
        creator.reset(new DataUserTeamRole(nullptr));
        break;
    case Elements::ElementType::RoleMenuItem:
        creator.reset(new DataRoleMenuItem(nullptr));
        break;
    case Elements::ElementType::Rule:
        creator.reset(new DataRule(nullptr));
        break;
    case Elements::ElementType::RuleState:
        creator.reset(new DataRuleState(nullptr));
        break;
    case Elements::ElementType::RuleItemType:
        creator.reset(new DataRuleItemType(nullptr));
        break;
    case Elements::ElementType::RuleProject:
        creator.reset(new DataRuleProject(nullptr));
        break;
    case Elements::ElementType::Project:
        creator.reset(new DataProject(nullptr));
        break;
    case Elements::ElementType::Phase:
        creator.reset(new DataPhase(nullptr));
        break;
    case Elements::ElementType::ItemHistory:
        creator.reset(new DataItemHistory(nullptr));
        break;
    case Elements::ElementType::ItemUserState:
        creator.reset(new DataItemUserState(nullptr));
        break;
    case Elements::ElementType::ItemField:
        creator.reset(new DataItemField(nullptr));
        break;

    default:
        LOG_WARN
            << "Обнаружен нереализованный объект с типом: "
            << unsigned(elementType);
        return {};
    }

    std::vector<std::shared_ptr<DataElement>> result;
    if (!ids.empty())
    {
        result.reserve(ids.size());
        for (auto element : creator->fromDatabaseElements(database, ids))
            result.emplace_back(creator->wrap(element));
        return result;
    }

    const auto elementIds = fromDatabaseIds(database, elementType);
    result.reserve(elementIds.size());
    for (auto element : creator->fromDatabaseElements(database, elementIds))
        result.emplace_back(creator->wrap(element));
    return result;
}

std::vector<unsigned> fromDatabaseIds(
    sqlite::database& database,
    Elements::ElementType elementType
)
{
    switch (elementType)
    {
    case Elements::ElementType::Item:
        return Database::DataItem::ids(database);
    case Elements::ElementType::ItemType:
        return Database::DataItemType::ids(database);
    case Elements::ElementType::FieldType:
        return Database::DataFieldType::ids(database);
    case Elements::ElementType::Workflow:
        return Database::DataWorkflow::ids(database);
    case Elements::ElementType::State:
        return Database::DataState::ids(database);
    case Elements::ElementType::Edge:
        return Database::DataEdge::ids(database);
    case Elements::ElementType::User:
        return Database::DataUser::ids(database);
    case Elements::ElementType::Role:
        return Database::DataRole::ids(database);
    case Elements::ElementType::Team:
        return Database::DataTeam::ids(database);
    case Elements::ElementType::UserTeamRole:
        return Database::DataUserTeamRole::ids(database);
    case Elements::ElementType::RoleMenuItem:
        return Database::DataRoleMenuItem::ids(database);
    case Elements::ElementType::Rule:
        return Database::DataRule::ids(database);
    case Elements::ElementType::RuleState:
        return Database::DataRuleState::ids(database);
    case Elements::ElementType::RuleItemType:
        return Database::DataRuleItemType::ids(database);
    case Elements::ElementType::RuleProject:
        return Database::DataRuleProject::ids(database);
    case Elements::ElementType::Project:
        return Database::DataProject::ids(database);
    case Elements::ElementType::Phase:
        return Database::DataPhase::ids(database);
    case Elements::ElementType::ItemHistory:
        return Database::DataItemHistory::ids(database);
    case Elements::ElementType::ItemUserState:
        return Database::DataItemUserState::ids(database);
    case Elements::ElementType::ItemField:
        return Database::DataItemField::ids(database);

    default:
        LOG_WARN
            << "Обнаружен нереализованный объект с типом: "
            << unsigned(elementType);
        return {};
    }
}

void removeFromDatabase(
    sqlite::database& database,
    Elements::ElementType elementType,
    const std::vector<unsigned>& ids
)
{
    if (ids.empty())
        return;

    switch (elementType)
    {
    case Elements::ElementType::Item:
        Database::DataItem::remove(database, ids);
        break;
    case Elements::ElementType::ItemType:
        Database::DataItemType::remove(database, ids);
        break;
    case Elements::ElementType::FieldType:
        Database::DataFieldType::remove(database, ids);
        break;
    case Elements::ElementType::Workflow:
        Database::DataWorkflow::remove(database, ids);
        break;
    case Elements::ElementType::State:
        Database::DataState::remove(database, ids);
        break;
    case Elements::ElementType::Edge:
        Database::DataEdge::remove(database, ids);
        break;
    case Elements::ElementType::User:
        Database::DataUser::remove(database, ids);
        break;
    case Elements::ElementType::Role:
        Database::DataRole::remove(database, ids);
        break;
    case Elements::ElementType::Team:
        Database::DataTeam::remove(database, ids);
        break;
    case Elements::ElementType::UserTeamRole:
        Database::DataUserTeamRole::remove(database, ids);
        break;
    case Elements::ElementType::RoleMenuItem:
        Database::DataRoleMenuItem::remove(database, ids);
        break;
    case Elements::ElementType::Rule:
        Database::DataRule::remove(database, ids);
        break;
    case Elements::ElementType::RuleState:
        Database::DataRuleState::remove(database, ids);
        break;
    case Elements::ElementType::RuleItemType:
        Database::DataRuleItemType::remove(database, ids);
        break;
    case Elements::ElementType::RuleProject:
        Database::DataRuleProject::remove(database, ids);
        break;
    case Elements::ElementType::Project:
        Database::DataProject::remove(database, ids);
        break;
    case Elements::ElementType::Phase:
        Database::DataPhase::remove(database, ids);
        break;
    case Elements::ElementType::ItemHistory:
        Database::DataItemHistory::remove(database, ids);
        break;
    case Elements::ElementType::ItemUserState:
        Database::DataItemUserState::remove(database, ids);
        break;
    case Elements::ElementType::ItemField:
        Database::DataItemField::remove(database, ids);
        break;

    default:
        LOG_WARN
            << "Обнаружен нереализованный объект с типом: "
            << unsigned(elementType);
        break;
    }
}

} // namespace Database
